/** ***************************************************************************
 * SugarTga.cpp
 * 2012 CoffeeParty Ltd.
 * Date Created: 2012-07-21                                  format YYYY-MM-DD
 * Revision Id: $Id$
 ******************************************************************************
 */

#include "CoffeeCommons.hpp"
#include "CoffeeLogger.hpp"
#include "CoffeeIO.hpp"

#include "SugarImage.hpp"

SugarTga::SugarTga(c_const_bytep filename)
: filesize(0)
{
    if (!isCompatibleFileFormat(filename, "tga"))
    {
        c_print("SugarTga(): File Format Error");
        return;
    }

    if (!open(filename))
    {
        c_print("SugarTga(): Reading Error");
        return;
    }
}

SugarTga::~SugarTga()
{
    if (imgdata)
        rdrop(imgdata);
}

c_size_t SugarTga::getSize() const
{
    return filesize;
}

c_size_t SugarTga::getImageDataSizeInBytes() const
{
    return imgsize;
}

c_size_t SugarTga::getImageDataSizeInPixel() const
{
    return imgwidth * imgheight;
}

c_uint SugarTga::getBitsPerPixel() const
{
    return imgbpp;
}

c_uint SugarTga::getBytesPerPixel() const
{
    return imgbytespp;
}

c_uint SugarTga::getPitch() const
{
    return imgpitch;
}

c_uint SugarTga::getRedMask() const
{
    switch (imgbpp)
    {
    case 8:
        return 0x1F << 11;
    case 16:
        return 0x1F << 10;
    case 24:
    case 32:
        return 0x00FF0000;
    default:
        return 0;
    }
}

c_uint SugarTga::getGreenMask() const
{
    switch (imgbpp)
    {
    case 8:
        return 0x3F << 5;
    case 16:
        return 0x1F << 5;
    case 24:
    case 32:
        return 0x0000FF00;
    default:
        return 0;
    }
}

c_uint SugarTga::getBlueMask() const
{
    switch (imgbpp)
    {
    case 8:
    case 16:
        return 0x1F;
    case 24:
    case 32:
        return 0x000000FF;
    default:
        return 0;
    }
}

c_uint SugarTga::getAlphaMask() const
{
    switch (imgbpp)
    {
    case 8:
        return 0;
    case 16:
        return 0x1 << 15;
    case 24:
        return 0;
    case 32:
        return 0xFF000000;
    default:
        return 0;
    }
}

c_color SugarTga::getPixel(c_uint x, c_uint y) const
{
    if (x >= imgwidth || y >= imgheight)
        return c_color(0);

    switch (imgbpp)
    {
    case 8:
    case 16:
        return c_color(((c_ushort*)imgdata)[y * imgwidth + x]);
    case 24:
        return c_color(((c_uint*)imgdata)[y * imgwidth + x]);
    case 32:
        c_ubytep p = imgdata + (y * 3) * imgwidth + (x * 3);
        return c_color(255, p[0], p[1], p[2]);
    }

    return c_color(0);
}

c_void SugarTga::setPixel(c_uint x, c_uint y,
    const c_color &color, c_bool blend)
{
    if (x >= imgwidth || y >= imgheight)
        return;

    switch (imgbpp)
    {
    case 8:
    case 16:
        {
            c_ushort *dest = (c_ushort*)(imgdata + (y * imgpitch) + (x << 1));
            *dest = color.color;
        }
        break;
    case 24:
        {
            c_ubytep dest = imgdata + (y + imgpitch) + (x * 3);
            dest[0] = (c_ubyte)color.red();
            dest[1] = (c_ubyte)color.green();
            dest[2] = (c_ubyte)color.blue();
        }
        break;
    case 32:
        {
            c_uint *dest = (c_uint*)(imgdata + (y + imgpitch) + (x << 2));
            *dest = color.color;
        }
        break;
    }
}

c_voidp SugarTga::ark()
{
    return imgdata;
}

c_void SugarTga::unark() { }

c_voidp SugarTga::getData() const
{
    return imgdata;
}

c_uint SugarTga::getWidth() const
{
    return imgwidth;
}

c_uint SugarTga::getHeight() const
{
    return imgheight;
}

c_bool SugarTga::hasAlpha() const
{
    return (imgbpp == 32);
}

c_void SugarTga::showOGL()
{
    glDisable(GL_DEPTH_TEST); //!< Disable Z coordinates and depth buffer
    glPixelStorei(GL_UNPACK_ROW_LENGTH, imgwidth);
    if (getBitsPerPixel() == 32)
    {
        glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
        glDrawPixels(imgwidth, imgheight, GL_RGBA, GL_UNSIGNED_BYTE, imgdata);
    }
    else if (getBitsPerPixel() == 24)
    {
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glDrawPixels(imgwidth, imgheight, GL_RGB, GL_UNSIGNED_BYTE, imgdata);
    }
    glEnable(GL_DEPTH_TEST);
}

c_bool SugarTga::open(c_const_bytep filename)
{
    SI_TGAHeader tgahead;
    CoffeeFile *qptr = new CoffeeFile(filename);
    if (!qptr->isOpen())
        return false;

    filesize = qptr->size();
    qptr->read((c_bytep)&tgahead, sizeof(SI_TGAHeader));

#ifdef __BIG_ENDIAN__
    tgahead.colormaplength = ByteSwap(tgahead.colormaplength);
    tgahead.width = ByteSwap(tgahead.width);
    tgahead.height = ByteSwap(tgahead.height);
#endif

    imgwidth = tgahead.width;
    imgheight = tgahead.height;
    imgbpp = tgahead.bpp;
    imgbytespp = imgbpp / 8;
    imgpitch = imgwidth * imgbytespp;
    imgsize = imgheight * imgpitch;
    imgdata = new c_ubyte[imgsize];
    qptr->read((c_bytep)imgdata, imgsize);

    if (imgbpp == 24 || imgbpp == 32)
    {
        for (c_uint i = 0; i < imgsize; i += imgbytespp)
        {
            c_byte tmp = imgdata[i];
            imgdata[i] = imgdata[i + 2];
            imgdata[i + 2] = tmp;
        }

        ///< Flip the TGA data upsidedown
        c_int linelength = imgwidth * imgbytespp;
        c_ubytep line1 = imgdata;
        c_ubytep line2 = &imgdata[linelength * (imgheight - 1)];
        for (; line1 < line2; line2 -= (linelength * 2))
        {
            for (c_int i = 0; i != linelength; line1++, line2++, i++)
            {
                c_byte tmp = *line1;
                *line1 = *line2;
                *line2 = tmp;
            }
        }
    }
    else
    {
        return false;
    }

    drop(qptr);
    return true;
}

c_bool SugarTga::isCompatibleFileFormat(c_const_bytep filename, c_const_bytep ext)
{
    c_string tmp = filename;
    c_int epos = tmp.findLast('.');
    if (epos < 0)
        return false;

    epos++;
    if (tmp.equals_ss_ic(ext, epos))
        return true;

    return false;
}

c_dimension<c_uint> SugarTga::getDimension() const
{
    return c_dimension<c_uint>(imgwidth, imgheight);
}

c_void SugarTga::writeToDisk(c_const_bytep filename)
{
    SI_TGAHeader header;
    header.idlength = 0;
    header.colormaptype = 0;
    header.datatypecode = 0;
    header.colormaporigin[0] = 0;
    header.colormaporigin[1] = 0;
    header.colormaplength = 0;
    header.colormapdepth = 0;
    header.xorigin[0] = 0;
    header.xorigin[1] = 0;
    header.yorigin[0] = 0;
    header.yorigin[1] = 0;
    header.width = this->getDimension().x;
    header.height = this->getDimension().y;

    header.imagedescriptor = (1 << 5);

    switch(this->getBitsPerPixel())
    {
    case CPCF_A8R8G8B8:
        header.bpp = 32;
        header.imagedescriptor |= 8;
        break;
    case CPCF_A1R5G5B5:
        header.bpp = 16;
        header.imagedescriptor |= 1;
        break;
    case CPCF_R5G6B5:
        header.bpp = 16;
        header.imagedescriptor |= 1;
        break;
    case CPCF_R8G8B8:
        header.bpp = 24;
        header.imagedescriptor |= 0;
        break;
    }

    CoffeeFile *qptr = new CoffeeFile(filename);

    if (qptr->write(&header, sizeof(SI_TGAHeader)) != sizeof(SI_TGAHeader))
        return;

    /// c_uint pixel_size = this->getBytesPerPixel();
    /// c_uint row_stride = (pixel_size * header.width);
    c_int row_size = ((header.bpp / 8) * header.width);
    c_ubytep row_ptr = (c_ubytep)malloc(row_size);

    c_uint y;
    for (y = 0; y < header.height; ++y)
    {
        if (qptr->write(row_ptr, row_size) != (c_uint)row_size)
            break;
    }

    free(row_ptr);

    SI_TGAFooter footer;
    footer.extoffset = 0;
    footer.devoffset = 0;
    strncpy(footer.signature, "TRUEVISION-XFILE.", 18);

    if (qptr->write(&footer, sizeof(SI_TGAFooter)) < (c_int)sizeof(SI_TGAFooter))
        return;
}

SugarTga* OpenTGA(c_const_bytep file)
{
    return new SugarTga(file);
}

c_void DeleteTGA(SugarTga* ptr)
{
    if (ptr)
        drop(ptr);
}


